//===--- ArgsToFrontendInputsConverter.cpp --------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "polarphp/frontend/internal/ArgsToFrontendInputsConverter.h"
#include "polarphp/frontend/internal/ArgsToFrontendOutputsConverter.h"
#include "polarphp/ast/DiagnosticsFrontend.h"
#include "polarphp/basic/Defer.h"
#include "polarphp/frontend/FrontendOptions.h"
#include "polarphp/option/Options.h"
#include "polarphp/llparser/Lexer.h"
#include "polarphp/global/NameStrings.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/LineIterator.h"
#include "llvm/Support/Path.h"

using namespace polar;
using namespace llvm::opt;

ArgsToFrontendInputsConverter::ArgsToFrontendInputsConverter(
   DiagnosticEngine &diags, const ArgList &args)
   : Diags(diags), Args(args),
     FilelistPathArg(args.getLastArg(options::OPT_filelist)),
     PrimaryFilelistPathArg(args.getLastArg(options::OPT_primary_filelist)) {}

Optional<FrontendInputsAndOutputs> ArgsToFrontendInputsConverter::convert(
   SmallVectorImpl<std::unique_ptr<llvm::MemoryBuffer>> *buffers) {
   POLAR_DEFER {
      if (buffers) {
         std::move(ConfigurationFileBuffers.begin(),
                   ConfigurationFileBuffers.end(),
                   std::back_inserter(*buffers));
         // Clearing the original list of buffers isn't strictly necessary, but
         // makes the behavior more sensible if we were to call convert() again.
         ConfigurationFileBuffers.clear();
      }
   };

   if (enforceFilelistExclusion())
      return None;

   if (FilelistPathArg ? readInputFilesFromFilelist()
                       : readInputFilesFromCommandLine())
      return None;
   Optional<std::set<StringRef>> primaryFiles = readPrimaryFiles();
   if (!primaryFiles)
      return None;

   FrontendInputsAndOutputs result;
   std::set<StringRef> unusedPrimaryFiles;
   std::tie(result, unusedPrimaryFiles) =
      createInputFilesConsumingPrimaries(*primaryFiles);

   if (diagnoseUnusedPrimaryFiles(unusedPrimaryFiles))
      return None;

   // Must be set before iterating over inputs needing outputs.
   result.setBypassBatchModeChecks(
      Args.hasArg(options::OPT_bypass_batch_mode_checks));

   return std::move(result);
}

bool ArgsToFrontendInputsConverter::enforceFilelistExclusion() {
   if (Args.hasArg(options::OPT_INPUT) && FilelistPathArg) {
      Diags.diagnose(SourceLoc(),
                     diag::error_cannot_have_input_files_with_file_list);
      return true;
   }
   // The following is not strictly necessary, but the restriction makes
   // it easier to understand a given command line:
   if (Args.hasArg(options::OPT_primary_file) && PrimaryFilelistPathArg) {
      Diags.diagnose(
         SourceLoc(),
         diag::error_cannot_have_primary_files_with_primary_file_list);
      return true;
   }
   return false;
}

bool ArgsToFrontendInputsConverter::readInputFilesFromCommandLine() {
   bool hadDuplicates = false;
   for (const Arg *A :
      Args.filtered(options::OPT_INPUT, options::OPT_primary_file)) {
      hadDuplicates = addFile(A->getValue()) || hadDuplicates;
   }
   return false; // FIXME: Don't bail out for duplicates, too many tests depend
   // on it.
}

bool ArgsToFrontendInputsConverter::readInputFilesFromFilelist() {
   bool hadDuplicates = false;
   bool hadError =
      forAllFilesInFilelist(FilelistPathArg, [&](StringRef file) -> void {
         hadDuplicates = addFile(file) || hadDuplicates;
      });
   if (hadError)
      return true;
   return false; // FIXME: Don't bail out for duplicates, too many tests depend
   // on it.
}

bool ArgsToFrontendInputsConverter::forAllFilesInFilelist(
   Arg const *const pathArg, llvm::function_ref<void(StringRef)> fn) {
   if (!pathArg)
      return false;
   StringRef path = pathArg->getValue();
   llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> filelistBufferOrError =
      llvm::MemoryBuffer::getFile(path);
   if (!filelistBufferOrError) {
      Diags.diagnose(SourceLoc(), diag::cannot_open_file, path,
                     filelistBufferOrError.getError().message());
      return true;
   }
   for (auto file :
      llvm::make_range(llvm::line_iterator(*filelistBufferOrError->get()),
                       llvm::line_iterator()))
      fn(file);
   ConfigurationFileBuffers.push_back(std::move(*filelistBufferOrError));
   return false;
}

bool ArgsToFrontendInputsConverter::addFile(StringRef file) {
   if (Files.insert(file))
      return false;
   Diags.diagnose(SourceLoc(), diag::error_duplicate_input_file, file);
   return true;
}

Optional<std::set<StringRef>>
ArgsToFrontendInputsConverter::readPrimaryFiles() {
   std::set<StringRef> primaryFiles;
   for (const Arg *A : Args.filtered(options::OPT_primary_file))
      primaryFiles.insert(A->getValue());
   if (forAllFilesInFilelist(
      PrimaryFilelistPathArg,
      [&](StringRef file) -> void { primaryFiles.insert(file); }))
      return None;
   return primaryFiles;
}

std::pair<FrontendInputsAndOutputs, std::set<StringRef>>
ArgsToFrontendInputsConverter::createInputFilesConsumingPrimaries(
   std::set<StringRef> primaryFiles) {
   bool hasAnyPrimaryFiles = !primaryFiles.empty();

   FrontendInputsAndOutputs result;
   for (auto &file : Files) {
      bool isPrimary = primaryFiles.count(file) > 0;
      result.addInput(InputFile(file, isPrimary));
      if (isPrimary)
         primaryFiles.erase(file);
   }

   if (!Files.empty() && !hasAnyPrimaryFiles) {
      Optional<std::vector<std::string>> userSuppliedNamesOrErr =
         OutputFilesComputer::getOutputFilenamesFromCommandLineOrFilelist(Args,
                                                                          Diags);
      if (userSuppliedNamesOrErr && userSuppliedNamesOrErr->size() == 1)
         result.setIsSingleThreadedWMO(true);
   }

   return {std::move(result), std::move(primaryFiles)};
}

bool ArgsToFrontendInputsConverter::diagnoseUnusedPrimaryFiles(
   std::set<StringRef> primaryFiles) {
   for (auto &file : primaryFiles) {
      // Catch "swiftc -frontend -c -filelist foo -primary-file
      // some-file-not-in-foo".
      assert(FilelistPathArg && "Unused primary with no filelist");
      Diags.diagnose(SourceLoc(), diag::error_primary_file_not_found, file,
                     FilelistPathArg->getValue());
   }
   return !primaryFiles.empty();
}
